﻿// Copyright 2013 Ronald Schlenker, www.technewlogic.de.
// 
// This file is part of Byond.
// 
// Byond is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
// 
// Byond is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with Byond.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Autofac.Core;
using Autofac;
using Module = Autofac.Module;

namespace Utils.CtorSubstitution
{
	public class CtorSubstitutionModule : Module
	{
		private readonly Stack<Type> _resolveStack = new Stack<Type>();

		protected override void AttachToComponentRegistration(
			IComponentRegistry componentRegistry,
			IComponentRegistration registration)
		{
			registration.Activating += (s, e) =>
			{
				// Get all readonly fields that can be resolved by the container
				var injectionFields = GetFields(e.Instance.GetType())
					.Where(it => !it.FieldType.IsPrimitive)
					.Where(it => it.IsInitOnly)
					.Where(it => !it.GetCustomAttributes(typeof(IgnoreAttribute), true).Any())
					.Where(it => e.Context.IsRegistered(it.FieldType))
					.Where(it => it.GetValue(e.Instance) == null)
					.ToList();

				foreach (var it in injectionFields)
				{
					var param = e.Parameters
						.OfType<TypedParameter>()
						.FirstOrDefault(it2 => it.FieldType.IsInstanceOfType(it2.Value));

					object injectionValue;
					if (param != null)
					{
						injectionValue = param.Value;
					}
					else
					{
						_resolveStack.Push(it.FieldType);
						injectionValue = e.Context.Resolve(it.FieldType);
						_resolveStack.Pop();
					}

					it.SetValue(e.Instance, injectionValue);
				}

				// TODO: Redundanzen zw Property unf Field

				// Get all properties with private setter that can be resolved by the container
				GetProperties(e.Instance.GetType())
					.Where(it => it.GetCustomAttributes(typeof(InAttribute), true).Any())
					.Where(it => it.GetSetMethod(true) != null)
					.Where(it => !it.PropertyType.IsPrimitive)
					.Where(it => e.Context.IsRegistered(it.PropertyType))
					.Where(it => it.GetValue(e.Instance, null) == null)
					.ToList()
					.ForEach(it =>
						{
							var param = e.Parameters
								.OfType<TypedParameter>()
								.FirstOrDefault(it2 => it.PropertyType.IsInstanceOfType(it2.Value));

							object injectionValue;
							if (param != null)
							{
								injectionValue = param.Value;
							}
							else
							{
								_resolveStack.Push(it.PropertyType);
								injectionValue = e.Context.Resolve(it.PropertyType);
								_resolveStack.Pop();
							}

							it.SetValue(e.Instance, injectionValue, null);
						});

				// Hier KEIN Exception-Handling im ForEach.
				// invoke the init method if we have one
				e.Instance.GetType()
					.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
					.Where(it => it.Name == "Ctor")
					.Where(it => it.ReturnType == typeof(void))
					.Where(it => !it.GetParameters().Any())
					.ToList()
					.ForEach(it => it.Invoke(e.Instance, null));
			};
		}

		private static IEnumerable<FieldInfo> GetFields(Type type)
		{
			return type == null
				? Enumerable.Empty<FieldInfo>()
				: GetFields(type.BaseType)
					.Concat(type
						.GetFields(BindingFlags.Instance | BindingFlags.NonPublic)
						.ToList())
					.ToList();
		}

		private static IEnumerable<PropertyInfo> GetProperties(Type type)
		{
			return type == null
				? Enumerable.Empty<PropertyInfo>()
				: GetProperties(type.BaseType)
					.Concat(type
						.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
						.ToList())
					.ToList();
		}
	}
}